
#include "graphics/model.h"
#include "graphics/scenemanager.h"
#include "graphics/materialmanager.h"
#include "graphics/mesh.h"
#include "graphics/animation.h"
#include "graphics/camera.h"
#include "graphics/light.h"
#include "graphics/material.h"
#include "graphics/node.h"

namespace dw
{
namespace graphics
{

																	Model::Model(const std::string& name) : _hierarchy(0)
{
}

																	Model::~Model()
{
	if (_hierarchy) delete _hierarchy;
}

Material* Model::getMaterial(const std::string& name)
{
	MaterialIterator materialIter = _materialByName.find(name);
	if (materialIter == _materialByName.end())
		return 0;
	return materialIter->second;
}

void Model::addMaterial(Material* material)
{
	if (material == 0) throw std::invalid_argument(__FUNCTION__);

	MaterialIterator materialIter = _materialByName.find(material->getName());
	if (materialIter == _materialByName.end())
		_materialByName.insert(std::make_pair(material->getName(), material));
}

void Model::removeMaterial(const Material* material)
{
	if (material == 0) throw std::invalid_argument(__FUNCTION__);
	
	_materialByName.erase(material->getName());
}

Model* Model::fromStream(std::istream& is)
{
	Model* nrv = new Model();

	is >> *nrv;
	return nrv;
}

Model* Model::fromFile(const std::string& filename)
{
	Model* nrv;
	std::ifstream file(filename.c_str(), std::ios::binary);

	if (!file.is_open())
	{
		std::string message = std::string(__FUNCTION__) + std::string("(filename='") + filename + std::string("')");
		throw std::invalid_argument(message);
	}
		
	nrv = fromStream(file);
	file.close();

	return nrv;
}

void Model::draw()
{
	_hierarchy->render();
}

std::ostream& operator<<	(std::ostream& os, Model& model)
{
	std::map< Node*, unsigned >									nodeIndices;
	std::map< Node*, unsigned >::iterator nodeIndexIter;
	//std::map< Material*, unsigned>						materialIndices;
	std::map< Object*, unsigned >								objectIndices;
	std::map< Object*, unsigned >::iterator objectIndexIter;
	std::map< Animation*, unsigned >						animationIndices;
	std::map< Animation*, unsigned >::iterator animationIndexIter;
	std::queue< Node* >													nodeQueue;

	nodeQueue.push(model._hierarchy);
	while (!nodeQueue.empty())
	{
		Node* currentNode		= nodeQueue.front();
		unsigned numNodes			= static_cast< unsigned >(nodeIndices.size());
		unsigned numObjects		= static_cast< unsigned >(objectIndices.size());
		unsigned numAnimation	= static_cast< unsigned >(animationIndices.size());

		nodeIndices.insert(std::make_pair(currentNode, numNodes));
		if (currentNode->hasObject())	
			objectIndices.insert(std::make_pair(currentNode->getObject(), numObjects));
		if (currentNode->hasAnimation())
			animationIndices.insert(std::make_pair(currentNode->getAnimation(), numAnimation));

		nodeQueue.pop();

		// push the children if they haven't already been processed
		for (unsigned i = 0; i < currentNode->getChildrenCount(); ++i)
			if (nodeIndices.find(currentNode->getChild(i)) == nodeIndices.end())
				nodeQueue.push(currentNode->getChild(i));
	}

	// Write materials
	unsigned numMaterials = model.getNumMaterials();
	os.write(reinterpret_cast< const char* >(&numMaterials), sizeof (unsigned));
	for (Model::MaterialIterator materialIter = model._materialByName.begin(); materialIter != model._materialByName.end(); ++materialIter)
		os << *(materialIter->second);

	// Write objects
	unsigned numObjects = static_cast< unsigned >(objectIndices.size());
	os.write(reinterpret_cast< const char* >(&numObjects), sizeof (unsigned));
	for (objectIndexIter = objectIndices.begin(); objectIndexIter != objectIndices.end(); ++objectIndexIter)
	{
		Object* currentObject	= objectIndexIter->first;

		Object::Type objectType		= currentObject->getType();

		os.write(reinterpret_cast< const char* >(&objectIndexIter->second), sizeof (unsigned));
		os.write(reinterpret_cast< const char* >(&objectType), sizeof (Object::Type));

		switch (objectType)
		{
		case Object::MESH		: os << *(static_cast< Mesh* >(currentObject));		break ;
		case Object::CAMERA	: os << *(static_cast< Camera* >(currentObject)); break ;
		//case Object::LIGHT	: os << *(static_cast< Light* >(currentObject));	break ;
		}
	}

	// Write animations
	unsigned numAnimations = static_cast< unsigned >(animationIndices.size());
	os.write(reinterpret_cast< const char* >(&numAnimations), sizeof (unsigned));
	for (animationIndexIter = animationIndices.begin(); animationIndexIter != animationIndices.end(); ++animationIndexIter)
	{
		Animation* currentAnimation = animationIndexIter->first;

		os.write(reinterpret_cast< const char* >(&animationIndexIter->second), sizeof (unsigned));
		os << *currentAnimation;
	}

	// Write nodes
	unsigned numNodes = static_cast< unsigned >(nodeIndices.size());
	os.write(reinterpret_cast< const char* >(&numNodes), sizeof (unsigned));
	for (nodeIndexIter = nodeIndices.begin(); nodeIndexIter != nodeIndices.end(); ++nodeIndexIter)
	{
		Node* currentNode = nodeIndexIter->first;
		unsigned numChildren = static_cast< unsigned >(currentNode->getChildrenCount());
		
		os.write(reinterpret_cast< const char* >(&nodeIndexIter->second), sizeof (unsigned));
		os << *currentNode;
		os.write(reinterpret_cast< const char* >(&numChildren), sizeof (unsigned));
		for (unsigned i = 0; i < numChildren; ++i)
			os.write(reinterpret_cast< const char* >(&nodeIndices[currentNode->getChild(i)]), sizeof (unsigned));
		bool hasObject = currentNode->hasObject();
		os.write(reinterpret_cast< const char* >(&hasObject), sizeof (bool));
		if (hasObject)
			os.write(reinterpret_cast< const char* >(&objectIndices[currentNode->getObject()]), sizeof (unsigned));
		bool hasAnimation = currentNode->hasAnimation();
		os.write(reinterpret_cast< const char* >(&hasAnimation), sizeof (bool));
		if (hasAnimation)
			os.write(reinterpret_cast< const char* >(&animationIndices[currentNode->getAnimation()]), sizeof (unsigned));
	}

	return os;
}
	
std::istream& operator>>(std::istream& is, Model& model)
{
	typedef std::vector< Node* >			NodeArray;
	typedef std::vector< Object* >		ObjectArray;
	typedef std::vector< Animation* >	AnimationArray;

	unsigned numMaterials;
	unsigned numNodes;
	unsigned numObjects;
	unsigned numAnimations;

	NodeArray nodes;
	ObjectArray objects;
	AnimationArray animations;

	// Read materials
	is.read(reinterpret_cast< char* >(&numMaterials), sizeof (unsigned));
	for (unsigned i = 0; i < numMaterials; ++i)
	{
		Material* material = new Material;
		is >> *material;
		if (Material* mat = MaterialManager::getInstance().getMaterial(material->getName()))
		{
			delete material;
			material = mat;
		}
		else
			MaterialManager::getInstance().addMaterial(material);
		model.addMaterial(material);
	}

	// Read objects
	is.read(reinterpret_cast< char* >(&numObjects), sizeof (unsigned));
	objects.resize(numObjects, 0);
	for (unsigned i = 0; i < numObjects; ++i)
	{
		Object* currentObject = 0;
		unsigned objectIndex;
		Object::Type objectType;

		is.read(reinterpret_cast< char* >(&objectIndex), sizeof (unsigned));
		is.read(reinterpret_cast< char* >(&objectType), sizeof (Object::Type));

		switch (objectType)
		{
		case Object::MESH	: 
			currentObject = new Mesh;
			is >> *(static_cast< Mesh* >(currentObject));
			break ;
		case Object::CAMERA	: 
			currentObject = new Camera;
			is >> *(static_cast< Camera* >(currentObject));
			break ;
		/*case Object::LIGHT	: 
			currentObject = new Light;
			is >> *(static_cast< Light* >(currentObject));
			break ;*/
		}
		objects[objectIndex] = currentObject;
	}

	// Read animations
	is.read(reinterpret_cast< char* >(&numAnimations), sizeof (unsigned));
	animations.resize(numAnimations, 0);
	for (unsigned i = 0; i < numAnimations; ++i)
	{
		Animation* currentAnimation = new Animation();
		unsigned animationIndex;

		is.read(reinterpret_cast< char* >(&animationIndex), sizeof (unsigned));
		is >> *currentAnimation;
		animations[animationIndex] = currentAnimation;
	}

	// Read the nodes
	is.read(reinterpret_cast< char* >(&numNodes), sizeof (unsigned));
	nodes.resize(numNodes, 0);
	for (unsigned i = 0; i < numNodes; ++i)
	{
		Node* currentNode = 0;
		unsigned nodeIndex;
		unsigned numChildren;

		is.read(reinterpret_cast< char* >(&nodeIndex), sizeof (unsigned)); 
		if (nodeIndex == 0)
		{
			model._hierarchy = new Node();
			currentNode = model._hierarchy;
		}
		else
			currentNode = new Node;
		is >> *currentNode;
		is.read(reinterpret_cast< char* >(&numChildren), sizeof (unsigned));
		currentNode->m_children.resize(numChildren, 0);
		for (unsigned j = 0; j < numChildren; ++j)
			is.read(reinterpret_cast< char* >(&currentNode->m_children[j]), sizeof (unsigned));
		bool hasObject = 0, hasAnimation = 0;
		is.read(reinterpret_cast< char* >(&hasObject), sizeof (bool));
		if (hasObject)
			is.read(reinterpret_cast< char* >(&currentNode->m_object), sizeof (unsigned));
		else
			currentNode->m_object = reinterpret_cast< Object* >(-1);
		is.read(reinterpret_cast< char* >(&hasAnimation), sizeof (bool));
		if (hasAnimation)
			is.read(reinterpret_cast< char* >(&currentNode->m_animation), sizeof (unsigned));
		else
			currentNode->m_animation = reinterpret_cast< Animation* >(-1);
		nodes[nodeIndex] = currentNode;
	}

	// Rebuild the node hierarchy (model graph) and reattach object & animation to their node
	for (unsigned i = 0; i < numNodes; ++i)
	{
		Node* currentNode = nodes[i];

		for (unsigned j = 0; j <  currentNode->getChildrenCount(); ++j)
		{
			currentNode->m_children[j] = nodes[reinterpret_cast< ObjectArray::size_type >(currentNode->m_children[j])];
			currentNode->m_children[j]->m_parent = currentNode;
		}
		if (reinterpret_cast< ObjectArray::size_type >(currentNode->m_object) != -1)
			currentNode->m_object = objects[reinterpret_cast< ObjectArray::size_type >(currentNode->m_object)];
		else
			currentNode->m_object = 0;
		if (reinterpret_cast< AnimationArray::size_type >(currentNode->m_animation) != -1)
			currentNode->m_animation = animations[reinterpret_cast< AnimationArray::size_type >(currentNode->m_animation)];
		else
			currentNode->m_animation = 0;
	}

	return is;
}

} // namespace graphics
} // namespace dw